/*
 * Copyright (c) 2009 Johnny Mast
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#import "Sound.h"

@interface Sound (Private)
	- (NSUInteger) nextAvailableSource;
	- (void) MusicPlayerPlay:(NSString*)file volume:(ALfloat)volume numloops:(NSInteger)numloops;
	- (void) PlaySound:(NSString*)filename shouldLoop:(BOOL)loop;
	- (void) setSoundLocation:(XFSoundLocation)location;
@end

@implementation Sound
@synthesize AudioSources;
@synthesize SoundCache;
@synthesize musicVolume;
@synthesize soundVolume;
@synthesize MusicCache;
@synthesize MusicPlayer;
@synthesize device;
@synthesize context;
@dynamic soundPosition;

static Sound* SoundObject;

#pragma mark -
#pragma mark Public methods

+ (Sound *)SoundEngine {
	@synchronized(self)
	{
		if (SoundObject == nil)
		{
			SoundObject = [[self alloc] init];
		}
	}
	return SoundObject;
}


- (id) init {
	[super init];
	musicVolume = 1.0f;
	soundVolume = 1.0f;
	
	AudioSources = [[NSMutableArray alloc] initWithCapacity:32]; /* Iphone can only have 32 source channels */
	SoundCache   = [[NSMutableDictionary alloc] initWithCapacity:10];
	MusicCache   = [[NSMutableDictionary alloc] initWithCapacity:10];
	
	BOOL result = [self InitOpenAL];
	if (!result)
		return nil;
	
	
	return self;
}


- (BOOL) InitOpenAL {
	
	ALenum	error;
	device = alcOpenDevice(nil);
	
	if (device)
	{
		context = alcCreateContext(device, NULL);
		alcMakeContextCurrent(context);
		
		NSUInteger sourceID;
		for (int index = 0; index < kMaxSourcChannels; index++) {
			alGenSources(1, &sourceID);
			
			if((error = alGetError()) != AL_NO_ERROR) {
#if defined(GAMENGINE_DEBUG)
				NSLog(@"Sound Engine: Error Generating Source: %x", error);
#endif
				return NO;
			} else
			[AudioSources addObject:[NSNumber numberWithUnsignedInt:sourceID]];
		}
		return YES;
	}
	return NO;	
}


- (void) shutDown {
	@synchronized(self)
	{
		if (SoundObject != nil)
		{
			for (int i = 0; i < [AudioSources count]; i++) 
				alDeleteSources(1, (ALuint*)[AudioSources objectAtIndex:i]);
			
			
			context = alcGetCurrentContext();
			device = alcGetContextsDevice(context);

			alcDestroyContext(context);
			alcCloseDevice(device);
			
			[self dealloc];
		}
	}
}

- (void) dealloc
{
	[MusicPlayer release];
	[AudioSources release];
	[SoundCache release];
	[MusicCache release];
	[super dealloc];
}

- (void)SetMusicVolume:(ALfloat)MusicVolume {
	musicVolume = MusicVolume;
}


- (void)BindMusic:(NSString *)filename ofType:(NSString*)filetype forKey:(NSString*)soundkey {
	[MusicCache setObject:[[NSBundle mainBundle] pathForResource:filename ofType:filetype] forKey:soundkey];
}


- (void)BindSound:(NSString *)filename ofType:(NSString*)filetype forKey:(NSString*)soundkey{
	[SoundCache setObject:[[NSBundle mainBundle] pathForResource:filename ofType:filetype] forKey:soundkey];
}


- (void)PlaySoundAtLocation:(XFSoundLocation)point withKey:(NSString *)soundkey shouldLoop:(BOOL)loop {
	[self setSoundLocation:point];
	[self PlaySound:[SoundCache objectForKey:soundkey] shouldLoop:loop];
}

- (void)PlaySoundWithKey:(NSString*)soundkey Loop:(BOOL)loop {
	[self PlaySound:[SoundCache objectForKey:soundkey] shouldLoop:loop];	
}


- (void)PlayBackgroundMusicWithKey:(NSString *)musickey volume:(ALfloat)soundvolume {
	NSString* path = [MusicCache objectForKey:musickey];
	[self MusicPlayerPlay:path volume:soundvolume numloops:1];
}


- (void)PlayBackgroundMusicWithKey:(NSString *)musickey {
	NSString* path = [MusicCache objectForKey:musickey];
	[self MusicPlayerPlay:path volume:musicVolume numloops:1];
}
@end


#pragma mark -
#pragma mark Private functions

@implementation Sound (Private)

- (void) setSoundLocation:(XFSoundLocation)location
{
	soundPosition = location;
	alListener3f(AL_POSITION, soundPosition.x, soundPosition.y, 0.0f);
}

- (NSUInteger) nextAvailableSource {
	
	// Holder for the current state of the current source
	NSInteger sourceState;
	
	// Find a source which is not being used at the moment
	for(NSNumber *sourceNumber in AudioSources) {
		alGetSourcei([sourceNumber unsignedIntValue], AL_SOURCE_STATE, &sourceState);
		// If this source is not playing then return it
		if(sourceState != AL_PLAYING) return [sourceNumber unsignedIntValue];
	}
	
	// If all the sources are being used we look for the first non looping source
	// and use the source associated with that
	NSInteger looping;
	for(NSNumber *sourceNumber in AudioSources) {
		alGetSourcei([sourceNumber unsignedIntValue], AL_LOOPING, &looping);
		if(!looping) {
			// We have found a none looping source so return this source and stop checking
			NSUInteger sourceID = [sourceNumber unsignedIntValue];
			alSourceStop(sourceID);
			return sourceID;
		}
	}
	
	// If there are no looping sources to be found then just use the first sounrce and use that
	NSUInteger sourceID = [[AudioSources objectAtIndex:0] unsignedIntegerValue];
	alSourceStop(sourceID);
	return sourceID;
}

- (void)MusicPlayerPlay:(NSString*)file volume:(ALfloat)volume numloops:(NSInteger)numloops
{
	NSError* error;
	
	// Initialize the AVAudioPlayer
	MusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:file] error:&error];
	
	// If the backgroundMusicPlayer object is nil then there was an error
	if(!MusicPlayer) {
		NSLog(@"ERROR SoundManager: Could not play music for key '%d'", error);
		return;
	}		
	
	// Set the number of times this music should repeat.  -1 means never stop until its asked to stop
	[MusicPlayer setNumberOfLoops:numloops];
	
	// Set the volume of the music
	[MusicPlayer setVolume:volume];
	
	// Play the music
	[MusicPlayer play];
}

-(void)PlaySound:(NSString*)filename shouldLoop:(BOOL)loop
{
	CFURLRef fileURL = (CFURLRef)[NSURL fileURLWithPath:filename];
	
	if (fileURL)
	{
		ALenum  format;
		ALsizei size;
		ALsizei freq;
		ALuint  buffer;
		NSUInteger source = [self nextAvailableSource];
		
		void* filedata = MyGetOpenALAudioData(fileURL, &size, &format, &freq);
		
		
		alGenBuffers(1, &buffer);
		
		// use the static buffer data API
		alBufferDataStaticProc(buffer, format, filedata, size, freq);
		
	//	NSLog(@"Sound pos = %f,%f,%f", soundPosition[0],soundPosition[1],soundPosition[2]);
		//alSourcefv(source, AL_POSITION, (ALfloat*)&soundPosition);
		alListenerfv(AL_POSITION, (ALfloat*)&soundPosition);
		
		// attach OpenAL Buffer to OpenAL Source
		alSourcei(source, AL_BUFFER, 0);
		alSourcei(source, AL_BUFFER, buffer);
		
		if (loop == YES)
			alSourcei(source, AL_LOOPING, AL_TRUE);
		else 
			alSourcei(source, AL_LOOPING, AL_FALSE);
		
		alSourcePlay(source);
	}	
}

@end